﻿using ArtMath.Attribute;
using ArtMath.Component.Circle;
using ArtMath.Component.Interface;
using ArtMath.Component.Point;
using ArtMath.Converter;
using ArtMath.Core.Data;
using ArtMath.Core.Helper;
using ArtMath.PointCollection;
using ArtMath.Helper;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using ArtMath.Component.CoordinateSystem;
using ArtMath.Resource;
using ArtMath.CommonType;

namespace ArtMath.Component.Line
{
    /// <summary>
    /// 三点圆弧
    /// </summary>
    [Serializable]
    public class ThreePointsArc : LineBase, ICircle, IArc
    {
        //double arcLen, chordLen, start, Pstart, sweep, Psweep;
        List<KeyValuePair<PointD, double[]>> track = new List<KeyValuePair<PointD, double[]>>();
        PartialCircle ske = new PartialCircle() { Curves = new Curve[] { new Curve() } };
        PartialEllipse pske = new PartialEllipse() { Curves = new Curve[] { new Curve() } };
        Curve newCur = new Curve(), newPCur = new Curve();
        PropertyDisplayer rDisplayer, ctDisplayer, angleDisplayer, arclenDisplayer, chordlenDisplayer;
        public ThreePointsArc(SketchPad pad, string name, bool addToList = true) : base(pad, name, ToolTypeName.sdyh, addToList)
        {
            InPoints = new PointBase[3];
            RadiusDisplayer = new PropertyDisplayer(this, "Radius");
            CenterDisplayer = new PropertyDisplayer(this, "Center");
            CentralAngleDisplayer = new PropertyDisplayer(this, "CentralAngle");
            ArcLengthDisplayer = new PropertyDisplayer(this, "ArcLength");
            ChordLengthDisplayer = new PropertyDisplayer(this, "ChordLength");
        }
        public override PointD MassPoint
        {
            get { return Center; }
        }
        [MultilingualCategory("Structure"), MultilingualDisplayName("{0}1", "EndPoint")]
        public PointBase Point1
        {
            get { return InPoints[0]; }
            set { SetPoint(0, value); }
        }
        [MultilingualCategory("Structure"), MultilingualDisplayName("{0}2", "EndPoint")]
        public PointBase Point2
        {
            get { return InPoints[1]; }
            set { SetPoint(1, value); }
        }
        [MultilingualCategory("Structure"), MultilingualDisplayName("PassPoint")]
        public PointBase MidPoint
        {
            get { return InPoints[2]; }
            set { SetPoint(2, value); }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("ArcLength")]
        public double ArcLength { get; private set; }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("ChordLength")]
        public double ChordLength { get; private set; }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("CentralAngle"), TypeConverter(typeof(IntelliAngleConverter))]
        public double CentralAngle
        {
            get { return Math.Abs(Sweep); }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("Radius"), ReadOnly(true)]
        public double Radius
        {
            get { return ske.Radius; }
            protected set
            {
                ske.Radius = value;
                if (Pad.Coordinate == null)
                {
                    pske.LongAxisHalfLength = pske.ShortAxisHalfLength = ske.Radius;
                }
                else
                {
                    pske.LongAxisHalfLength = Pad.Coordinate.GetPx(ske.Radius);
                    pske.ShortAxisHalfLength = Pad.Coordinate.GetPy(ske.Radius);
                }
                Pad.Canvas.Invalidate();
            }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("ArcCenter"), TypeConverter(typeof(IntelliPointConverter))]
        public PointD Center
        {
            get { return ske.Center; }
        }

        [Browsable(false)]
        public PointD PCenter
        {
            get { return pske.Center; }
        }
        [Browsable(false)]
        public PointD NewPCenter
        {
            get
            {
                return Pad.XYToPxy(NewCenter);
            }
        }

        [Browsable(false)]
        public double NewRadius
        {
            get
            {
                return NewCenter.DistanceTo(Point1.NewXY);
            }
        }

        [Browsable(false)]
        public PointD NewCenter
        {
            get
            {
                return Utility.GetCenter(Point1.NewXY, Point2.NewXY, MidPoint.NewXY);
            }
        }
        public override bool PointsHaveConstraint
        {
            get { return false; }
        }
        public override PointD AnchorPoint(PropertyDisplayer disp)
        {
            return PCenter;
        }
        //[Browsable(false)]
        //public Curve[] Curves
        //{
        //    get { return new Curve[] { cur }; }
        //}
        //[Browsable(false)]
        //public PaintableCurve[] NewCurves
        //{
        //    get
        //    {
        //        Pad.BuildArcSamples(newCur, NewCenter, NewRadius, NewStart, NewSweep);
        //        return new PaintableCurve[] { newCur };
        //    }
        //}
        [Browsable(false)]
        public double Start
        {
            get { return (ske as PartialCircle).Start; }
        }
        [Browsable(false)]
        public double NewStart
        {
            get
            {
                return NewCenter.GetAngle(Point1.NewXY);
            }
        }
        [Browsable(false)]
        public double Sweep
        {
            get { return (ske as PartialCircle).Sweep; }
        }
        [Browsable(false)]
        public double NewSweep
        {
            get
            {
                double tmp = Utility.GetAngle(NewCenter, Point1.NewXY, Point2.NewXY);
                if (Utility.OnSameSide(Point1.NewPxy, Point2.NewPxy, MidPoint.NewPxy, NewPCenter))
                {
                    tmp = Core.Helper.Angle.RadRound - tmp;
                }
                tmp *= Utility.Sign(Point1.NewPxy, MidPoint.NewPxy, Point2.NewPxy) * (Point1.NewPxy.X > MidPoint.NewPxy.X ? -1 : 1);
                return tmp;
            }
        }
        [Browsable(false)]
        public double PStart
        {
            get { return (pske as PartialEllipse).Start; }
        }
        [Browsable(false)]
        public double NewPStart
        {
            get
            {
                double tmp = NewPCenter.GetAngle(Point1.NewPxy);
                if (Pad.Coordinate != null)
                {
                    tmp += Pad.Coordinate.RotateAngle.RadValue;
                }
                return tmp;
            }
        }
        [Browsable(false)]
        public double PSweep
        {
            get { return (pske as PartialEllipse).Sweep; }
        }
        [Browsable(false)]
        public double NewPSweep
        {
            get
            {
                double tmp = Utility.GetAngle(NewPCenter, Point1.NewPxy, Point2.NewPxy);
                if (Utility.OnSameSide(Point1.NewPxy, Point2.NewPxy, MidPoint.NewPxy, NewPCenter))
                {
                    tmp = Core.Helper.Angle.RadRound - tmp;
                }
                tmp *= Utility.Sign(Point1.NewPxy, MidPoint.NewPxy, Point2.NewPxy) * (Point1.NewPxy.X > MidPoint.NewPxy.X ? 1 : -1);
                return tmp;
            }
        }
        //[Browsable(false)]
        //public PaintableCurve[] Curves
        //{
        //    get { return new PaintableCurve[] { cur }; }
        //}
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "Radius", "Displayer")]
        public PropertyDisplayer RadiusDisplayer
        {
            get { return rDisplayer; }
            set { rDisplayer = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "ArcCenter", "Displayer")]
        public PropertyDisplayer CenterDisplayer
        {
            get { return ctDisplayer; }
            set { ctDisplayer = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "CentralAngle", "Displayer")]
        public PropertyDisplayer CentralAngleDisplayer
        {
            get { return angleDisplayer; }
            set { angleDisplayer = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "ArcLength", "Displayer")]
        public PropertyDisplayer ArcLengthDisplayer
        {
            get { return arclenDisplayer; }
            set { arclenDisplayer = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "ChordLength", "Displayer")]
        public PropertyDisplayer ChordLengthDisplayer
        {
            get { return chordlenDisplayer; }
            set { chordlenDisplayer = value; }
        }
        public override void AddTracks()
        {
            track.Add(new KeyValuePair<PointD, double[]>(PCenter, new double[] { Pad.Coordinate == null ? 0 : -Pad.Coordinate.RotateAngle.DegreeValue, pske.LongAxisHalfLength, pske.ShortAxisHalfLength, PStart * Core.Helper.Angle.RToD, PSweep * Core.Helper.Angle.RToD }));
        }
        public override void ClearTracks()
        {
            track.Clear();
            base.ClearTracks();
        }
        public override void PaintTrack(Graphics gra)
        {
            if (ShowTrack_)
            {
                for (int i = 0; i < track.Count; i++)
                {
                    //if (item.Value[1] == 0 || item.Value[2] == 0) continue;
                    //gra.TranslateTransform((float).X, (float)item.Key.Y);
                    //if (Pad.Coordinate != null)
                    //{
                    //    gra.RotateTransform((float));
                    //}
                    //gra.DrawArc(TrackPen, (float)-item.Value[1], (float)-item.Value[2], (float)(item.Value[1] * 2), (float)(item.Value[2] * 2),
                    //    (float)item.Value[3], (float)item.Value[4]);
                    //if (Pad.Coordinate != null)
                    //{
                    //    gra.RotateTransform((float)item.Value[0]);
                    //}
                    //gra.TranslateTransform((float)-item.Key.X, (float)-item.Key.Y);
                    gra.DrawArc(TrackPen, track[i].Key, track[i].Value[0], track[i].Value[1], track[i].Value[2], track[i].Value[3], track[i].Value[4]);
                }
            }
            base.PaintTrack(gra);
        }
        public override void Paint(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Visible && IsCreationCompleted)
            {
                gra.DrawArc(LinePen, PCenter, Pad.Coordinate == null ? 0 : -Pad.Coordinate.RotateAngle.DegreeValue, pske.LongAxisHalfLength, pske.ShortAxisHalfLength, PStart * Core.Helper.Angle.RToD, PSweep * Core.Helper.Angle.RToD);
            }
            base.Paint(gra);
        }
        public override bool CanDrawVirtual
        {
            get
            {
                if (OrderIndex == 0)
                {
                    if (MidPoint != null)
                    {
                        return true;
                    }
                }
                else
                {
                    if (Point2 != null)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

        public override PointCollectionsManager PSkeleton
        {
            get
            {
                return new PointCollectionsManager(pske);
            }
        }

        public override PointCollectionsManager Skeleton
        {
            get
            {
                return new PointCollectionsManager(ske);
            }
        }

        public override PointCollectionsManager NewSkeleton
        {
            get
            {
                Pad.BuildArcSamples(newCur, null, NewCenter, NewRadius, NewStart, NewSweep);
                return new PointCollectionsManager(new PartialCircle(NewCenter, NewRadius, NewStart, NewSweep) { Curves = new Curve[] { newCur } });
            }
        }

        public override PointCollectionsManager NewPSkeleton
        {
            get
            {
                Pad.BuildArcSamples(null, newPCur, NewCenter, NewRadius, NewStart, NewSweep);
                PartialEllipse e = new PartialEllipse() { Center = NewPCenter, Start = NewPStart, Sweep = NewPSweep, Curves = new Curve[] { newPCur } };
                double r = NewRadius;
                if (Pad.Coordinate == null)
                {
                    e.LongAxisHalfLength = e.ShortAxisHalfLength = r;
                }
                else
                {
                    e.LongAxisHalfLength = Pad.Coordinate.GetPx(r);
                    e.ShortAxisHalfLength = Pad.Coordinate.GetPy(r);
                }
                return new PointCollectionsManager(e);
            }
        }
        [Browsable(false)]
        public double NewArcLength
        {
            get { return Math.Abs(NewSweep) * NewRadius; }
        }

        public override void PaintVirtualObjects(Graphics gra)
        {
            if (CanDrawVirtual)
            {
                gra.DrawArc(PCenter, Pad.Coordinate == null ? 0 : -Pad.Coordinate.RotateAngle.DegreeValue, pske.LongAxisHalfLength, pske.ShortAxisHalfLength, PStart * Core.Helper.Angle.RToD, PSweep * Core.Helper.Angle.RToD);
            }
            base.PaintVirtualObjects(gra);
        }
        //public override PointD SlideDestination(PointD currentLocation, double length, out bool reverse)
        //{
        //    PartialCircle pc = new PartialCircle(NewCenter, NewRadius, NewStart, NewSweep);
        //    return pc.SlideDestination(currentLocation, length, out reverse);
        //    reverse = false;
        //    length %= 2 * NewArcLength;
        //    double nowAngle = NewCenter.GetAngle(currentLocation),
        //        toStart = Utility.GetSweepAngle(nowAngle, NewStart, NewSweep >= 0),
        //        toEnd = Utility.GetSweepAngle(nowAngle, NewStart + NewSweep, NewSweep < 0),
        //        excess = Math.Abs(length) - (length > 0 ? toEnd : toStart) * NewRadius;
        //    if (excess <= 0)
        //    {
        //        return NewCenter.Offset(nowAngle + Math.Sign(NewSweep) * length / NewRadius, NewRadius);
        //    }
        //    else
        //    {
        //        if (length > 0)
        //        {
        //            excess -= NewArcLength;
        //            if (excess <= 0)
        //            {
        //                reverse = true;
        //                return NewCenter.Offset(NewStart - Math.Sign(NewSweep) * excess / NewRadius, NewRadius);
        //            }
        //            else
        //            {
        //                return NewCenter.Offset(NewStart + Math.Sign(NewSweep) * excess / NewRadius, NewRadius);
        //            }
        //        }
        //        else
        //        {
        //            if (excess <= NewArcLength)
        //            {
        //                reverse = true;
        //                return NewCenter.Offset(NewStart + Math.Sign(NewSweep) * excess / NewRadius, NewRadius);
        //            }
        //            else
        //            {
        //                return NewCenter.Offset(NewStart + NewSweep - Math.Sign(NewSweep) * (excess - NewArcLength) / NewRadius, NewRadius);
        //            }
        //        }
        //    }
        //}
        //protected override PointD GetAbsorbedPoint(PointD PLocation, bool getNew = false)
        //{
        //    PointD tmp = Pad.Coordinate == null ? PLocation : Pad.Coordinate.PxyToXY(PLocation);
        //    if (getNew)
        //    {
        //        if (Utility.InSweepRange(NewStart, NewSweep, NewCenter.GetAngle(tmp)))
        //        {
        //            return Pad.FixOnCircle(NewPCenter, NewRadius, PLocation);
        //        }
        //        else
        //        {
        //            return Utility.GetAngle(NewCenter, tmp, Point1.NewXY) < Utility.GetAngle(NewCenter, tmp, Point2.NewXY) ? Point1.NewPxy : Point2.NewPxy;
        //        }
        //    }
        //    if (Utility.InSweepRange(Start.RadValue, Sweep.RadValue, Center.GetAngle(tmp)))
        //    {
        //        return Pad.FixOnCircle(PCenter, Radius, PLocation);
        //    }
        //    else
        //    {
        //        return Utility.GetAngle(Center, tmp, Point1.ToPointD()) < Utility.GetAngle(Center, tmp, Point2.ToPointD()) ? Point1.Pxy : Point2.Pxy;
        //    }
        //}
        public override void InternalPrepare(PointBase movePoint, ShapeBase sender = null)
        {
            foreach (PointBase item in OutChildren_Point)
            {
                ControlledOutPointsOriginalInfo.Add(item, new double[] { Utility.GetSweepRate(Start, Sweep, Center.GetAngle(item.ToPointD())) });
            }
            base.InternalPrepare(movePoint, sender);
        }
        public override PointD GetOutFreePointRecommendNewLocation(PointBase pt, bool getXY = true)
        {
            PointD xy = NewCenter.Offset(NewStart + NewSweep * ControlledOutPointsOriginalInfo[pt][0], NewRadius);
            return getXY ? xy : Pad.XYToPxy(xy);
        }
        public override void InternalClean(PointBase movedPoint, ShapeBase sender = null)
        {
            ControlledOutPointsOriginalInfo.Clear();
            base.InternalClean(movedPoint, sender);
        }
        public PointD[] GetTangentPoints(PointD passPoint)
        {
            return CurveTangent.GetArcTangentPoints(NewCenter, NewRadius, NewStart, NewSweep, passPoint);
        }
        public override void CheckInRegion(RectangleD rect)
        {
            SetInPicker(rect.IntersectCurves(pske.Curves));
            base.CheckInRegion(rect);
        }
        public override void ComponentChanged(ShapeBase component)
        {
            double[] gets = Pad.CalcArcParams(Point1, MidPoint.NewXY, MidPoint.NewPxy, Point2.NewXY, Point2.NewPxy,
                ref ske.Center, ref pske.Center);
            if (gets != null)
            {
                Radius = gets[1];
                ArcLength = gets[2];
                ChordLength = gets[3];
                ske.Start = gets[4];
                pske.Start = gets[5];
                ske.Sweep = gets[6];
                pske.Sweep = gets[7];
                Pad.BuildArcSamples(ske.Curves[0], pske.Curves[0], Center, Radius, Start, Sweep);
            }
            base.ComponentChanged(component);
        }
        public override PropertyDisplayer[] PropertyDisplayersCheckNearBy(PointD Plocation)
        {
            List<PropertyDisplayer> res = new List<PropertyDisplayer>(base.PropertyDisplayersCheckNearBy(Plocation));
            if (RadiusDisplayer.InRegion(Plocation))
            {
                res.Add(RadiusDisplayer);
            }
            if (CenterDisplayer.InRegion(Plocation))
            {
                res.Add(CenterDisplayer);
            }
            if (CentralAngleDisplayer.InRegion(Plocation))
            {
                res.Add(CentralAngleDisplayer);
            }
            if (ArcLengthDisplayer.InRegion(Plocation))
            {
                res.Add(ArcLengthDisplayer);
            }
            if (ChordLengthDisplayer.InRegion(Plocation))
            {
                res.Add(ChordLengthDisplayer);
            }
            return res.ToArray();
        }
        protected override string getFormula()
        {
            if (Pad.CoordinateType == CoordinateSystemType.PolarCoordinate)
            {
                PointD rt = (Pad.Coordinate as PolarCoordinate).XYtoRT(Center, false);
                return $"ρ^2+{rt.X}^2-{2 * rt.X}ρcos(θ-{rt.Y})={Radius * Radius}";
            }
            else
            {
                return $"(x-{Center.X})^2+(y-{Center.Y})^2={Radius * Radius}";
            }
        }
        public override void ComponentBuilding(Base component)
        {
            double[] gets;
            if (OrderIndex == 0)
            {
                if (MidPoint == null || (gets = Pad.CalcArcParams(Point1, MidPoint.ToPointD(), MidPoint.Pxy, null, SpareCoordinate,
                    ref ske.Center, ref pske.Center)) == null)
                {
                    ShowStatus($"{Resources.Translate("Creating")}{Resources.ColonSeparate}{Name}");
                    return;
                }
            }
            else
            {
                if (Point2 == null || (gets = Pad.CalcArcParams(Point1, null, SpareCoordinate, Point2.ToPointD(), Point2.Pxy,
                    ref ske.Center, ref pske.Center)) == null)
                {
                    ShowStatus($"{Resources.Translate("Creating")}{Resources.ColonSeparate}{Name}");
                    return;
                }
            }
            Radius = gets[1];
            ArcLength = gets[2];
            ChordLength = gets[3];
            ske.Start = gets[4];
            pske.Start = gets[5];
            ske.Sweep = gets[6];
            pske.Sweep = gets[7];
            Pad.BuildArcSamples(ske.Curves[0], pske.Curves[0], Center, Radius, Start, Sweep);
            ShowStatus($"{Resources.Translate("Creating")}{Resources.ColonSeparate}{Name}{Resources.LeftParenthesis}{Resources.Translate("ArcLength")} = {ArcLength}{Resources.CommaSeparate}{Resources.Translate("ChordLength")} = {ChordLength}{Resources.CommaSeparate}{Resources.Translate("ArcCenter")}{Resources.ColonSeparate}{Pad.ShowPoint(Center)}{Resources.CommaSeparate}{Resources.Translate("Radius")} = {Radius}{Resources.CommaSeparate}{Resources.Translate("CentralAngle")} = {CentralAngle}）");
            base.ComponentBuilding(component);
        }
        public override void RefreshPropertyDisplayersText()
        {
            RadiusDisplayer.RefreshText();
            CenterDisplayer.RefreshText();
            CentralAngleDisplayer.RefreshText();
            ArcLengthDisplayer.RefreshText();
            ChordLengthDisplayer.RefreshText();
            base.RefreshPropertyDisplayersText();
        }
        public override void RefreshPropertyDisplayersLocation()
        {
            RadiusDisplayer.RefreshLocation();
            CenterDisplayer.RefreshLocation();
            CentralAngleDisplayer.RefreshLocation();
            ArcLengthDisplayer.RefreshLocation();
            ChordLengthDisplayer.RefreshLocation();
            base.RefreshPropertyDisplayersLocation();
        }
        public override void PaintProperties(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Visible)
            {
                RadiusDisplayer.Paint(gra);
                CenterDisplayer.Paint(gra);
                CentralAngleDisplayer.Paint(gra);
                ArcLengthDisplayer.Paint(gra);
                ChordLengthDisplayer.Paint(gra);
            }
            base.PaintProperties(gra);
        }
        public double? GetTangentLineSlope(PointD pt)
        {
            return -1 / Center.GetSlope(pt);
        }
        protected override void ClickOrder(IList<PointCreatingArgs> args, int clickTimes, Predicate<PointCreatingArgs> act)
        {
            if (act == null) return;
            if (OrderIndex == 0)
            {
                if (!act(args[0])) return;
                if (!act(args[2])) return;
                if (!act(args[1])) return;
                return;
            }
            base.ClickOrder(args, clickTimes, act);
        }
        public override void CreateComponents()
        {
            if (OrderIndex == 0)
            {
                if (Point1 == null)
                {
                    Point1 = Pad.CreatePoint($"{Resources.Translate("EndPoint")}1", SpareCoordinate_);
                    Pad.TrackObj = this;
                }
                else if (MidPoint == null)
                {
                    MidPoint = Pad.CreatePoint(Resources.Translate("PassPoint"), SpareCoordinate_, true, new PointCollectionBase[] { new Universal(Point1.ToPointD()) });
                }
                else
                {
                    Point2 = Pad.CreatePoint($"{Resources.Translate("EndPoint")}2", SpareCoordinate_, true, new PointCollectionBase[] { new Universal(Point1.ToPointD(), Point1.GetSlope(MidPoint)) });
                    base.CreateComponents();
                }
            }
            else
            {
                if (Point1 == null)
                {
                    Point1 = Pad.CreatePoint($"{Resources.Translate("EndPoint")}1", SpareCoordinate_);
                    Pad.TrackObj = this;
                }
                else if (Point2 == null)
                {
                    Point2 = Pad.CreatePoint($"{Resources.Translate("EndPoint")}2", SpareCoordinate_, true, new PointCollectionBase[] { new Universal(Point1.ToPointD()) });
                }
                else
                {
                    MidPoint = Pad.CreatePoint(Resources.Translate("PassPoint"), SpareCoordinate_, true, new PointCollectionBase[] { new Universal(Point1.ToPointD(), Point1.GetSlope(Point2)) });
                    base.CreateComponents();
                }
            }
        }
    }
}
